import {injector, InjectorService, LocalsContainer} from "../index.js";
import {ContextLogger, ContextLoggerOptions} from "./ContextLogger.js";

/**
 * Options for creating a DI context instance.
 *
 * @public
 */
export interface DIContextOptions extends Omit<ContextLoggerOptions, "dateStart"> {
  id: string;
  platform?: string;
}

/**
 * Execution context for dependency injection operations.
 *
 * Represents a single execution scope (typically an HTTP request) with its own
 * scoped provider instances, logger, and lifecycle management. Provides access to
 * request-scoped services and context-specific data.
 *
 * ### Usage
 *
 * ```typescript
 * import {DIContext} from "@tsed/di";
 *
 * const context = new DIContext({id: "req-123"});
 *
 * context.logger.info("Processing request");
 * const service = await context.injector.invoke(MyService, context.container);
 *
 * await context.destroy(); // Cleanup when done
 * ```
 *
 * @public
 */
export class DIContext {
  [x: string]: any;

  readonly PLATFORM: string = "DI";
  #container?: LocalsContainer;
  #cache?: Map<any, any>;
  #logger?: ContextLogger;

  constructor(public opts: DIContextOptions) {
    this.PLATFORM = opts.platform || this.PLATFORM;
  }

  /**
   * Logger attached to the context request.
   */
  get logger() {
    this.#logger = this.#logger || new ContextLogger(this.opts);
    return this.#logger;
  }

  /**
   * Request id generated by @@contextMiddleware@@.
   *
   * ::: tip
   * By default Ts.ED generate uuid like that `uuidv4().replace(/-/gi, ""))`.
   * Dash are removed to simplify tracking logs in Kibana
   * :::
   *
   * ::: tip
   * Request id can by customized by changing the server configuration.
   *
   * ```typescript
   * @Configuration({
   *   logger: {
   *     reqIdBuilder: createUniqId // give your own id generator function
   *   }
   * })
   * class Server {
   *
   * }
   * ```
   * :::
   *
   */
  get id() {
    return this.opts.id;
  }

  get dateStart() {
    return this.logger.dateStart;
  }

  get injector(): InjectorService {
    return injector();
  }

  get env() {
    return this.injector.settings.get("env");
  }

  /**
   * The request container used by the Ts.ED DI. It contains all services annotated with `@Scope(ProviderScope.REQUEST)`
   */
  get container() {
    return (this.#container = this.#container || new LocalsContainer());
  }

  destroy(): Promise<any> {
    return Promise.all([this.#container?.destroy(), this.#logger?.flush(true)]);
  }

  cache<Value = any>(key: string, cb: () => Value): Value {
    if (!this.has(key)) {
      this.set(key, cb());
    }

    return this.get(key);
  }

  async cacheAsync<Value = any>(key: string, cb: () => Promise<Value>): Promise<Value> {
    if (!this.has(key)) {
      this.set(key, await cb());
    }

    return this.get(key);
  }

  delete(key: any): boolean {
    return !!this.#cache?.delete(key);
  }

  get<T = any>(key: any): T {
    return this.#cache?.get(key);
  }

  has(key: any): boolean {
    return !!this.#cache?.has(key);
  }

  set(key: any, value: any): this {
    this.#cache = this.#cache || new Map<any, any>();
    this.#cache.set(key, value);
    return this;
  }
}

export type BaseContext = DIContext & TsED.Context;
